/* Functions to draw pictures. */

#include "p24FJ256GB110.h"
#include "Mikro.h"
#include "MikroPictures.h"
#include "MikroSPI.h"

// SD library headers
#include "fat.h"
#include "fat_config.h"
#include "partition.h"
#include "sd_raw.h"
#include "sd_raw_config.h"

#define CS _LATF12
#define SELECT(x) x = 0
#define DESELECT(x) x = 1

#define TFT_SCREEN_WIDTH (320)
#define TFT_SCREEN_HEIGHT (240)

/* image encoding: 
   0 - no encoding
   1 - RLE (run-length encoding)
 */
struct image {
	// image width
	uint16 X;
	// image height
	uint16 Y;
	// image encoding
	uint16 encoding;
};

// Local image buffer to reduce file I/O overhead.
#define LOCALBUF_MAX (2000)
uint16 localbuf[LOCALBUF_MAX];
// to keep track of last bytes read from localbuf
uint16 localbufindex = 0;
// to keep track of local buffer size
uint16 localbufsize = 0;
uint8 sd_raw_init_status = 0;

void DrawAffine(
		       // screen bounds
		       uint16 X1, uint16 Y1, uint16 X2, uint16 Y2,
		       // memory bounds
		       uint16 PX1, uint16 PY1, uint16 PX2, uint16 PY2,
		       char *filename, struct image *im,
		       struct fat_file_struct *fd);
void GetRow(struct fat_file_struct *fd, struct image *im, uint16 * rowbuf,
	    uint16 rowsize);
void GetRowRLE(struct fat_file_struct *fd, struct image *im,
	       uint16 * rowbuf, uint16 rowsize);
void DumpLocalBuf(void);
void ReadImageHeader(struct fat_file_struct *fd, struct image *im);
void SetupFilesystem(struct partition_struct **partition,
		     struct fat_fs_struct **fs,
		     struct fat_dir_entry_struct *directory,
		     struct fat_dir_struct **dd);
static inline uint16 min(uint16 A, uint16 B);

void DrawPicture(uint16 upperleftX, uint16 upperleftY, char *filename)
{
	struct partition_struct *partition;
	struct fat_fs_struct *fs;
	struct fat_dir_entry_struct directory;
	struct fat_dir_struct *dd;

	struct fat_file_struct *fd;
	struct image im;


	SetupFilesystem(&partition, &fs, &directory, &dd);
	fd = open_file_in_dir(fs, dd, filename);
	ReadImageHeader(fd, &im);

	DrawAffine(upperleftX, upperleftY, upperleftX + im.X - 1,
		   upperleftY + im.Y - 1, 0, 0, im.X - 1, im.Y - 1,
		   filename, &im, fd);

	fat_close_file(fd);
	fat_close_dir(dd);
	fat_close(fs);
	partition_close(partition);
}

/* Draw part of a picture from memory onto the screen, scaling to fit.
 * -(X1,Y1),(X2,Y2) are the corners of the image on the screen.
 * -(PX1,PY1),(PX2,PY2) are the corners of the section of the image in memory
 *    to draw.
 */
void DrawAffine(
		       // screen bounds
		       uint16 X1, uint16 Y1, uint16 X2, uint16 Y2,
		       // memory bounds
		       uint16 PX1, uint16 PY1, uint16 PX2, uint16 PY2,
		       char *filename, struct image *im,
		       struct fat_file_struct *fd)
{
	// bounds-checking
	if (PX2 >= im->X || PY2 >= im->Y) {
		return;		// TODO some sort of error-handling
	}
	uint16 counterX, counterY;

	// datarow and newdatarow will be used each time a new row is drawn
	// to check whether a new row must be buffered
	uint16 datarow = PY1;
	uint16 newdatarow;

	uint16 rowsize = im->X;
	uint16 rowbuf[rowsize];

	// buffer first row of pixels
	if (im->encoding == 0) {
		// no encoding
		GetRow(fd, im, rowbuf, rowsize);
	} else if (im->encoding == 1) {
		// RLE encoding
		GetRowRLE(fd, im, rowbuf, rowsize);
	}

	SELECT(CS);
	for (counterY = Y1; counterY <= min(Y2, TFT_SCREEN_HEIGHT - 1);
	     counterY++) {
		newdatarow =
		    (counterY - Y1) * (PY2 - PY1) / (Y2 - Y1) + PY1;

		// get next row of pixels
		while (datarow < newdatarow) {
			if (im->encoding == 0) {
				// no encoding
				GetRow(fd, im, rowbuf, rowsize);
			} else if (im->encoding == 1) {
				// RLE encoding
				GetRowRLE(fd, im, rowbuf, rowsize);
			}
			datarow++;
		}

		// draw the row
		SetLocation(X1, counterY);
		for (counterX = X1;
		     counterX <= min(X2, TFT_SCREEN_WIDTH - 1);
		     counterX++) {
			WritePixel(rowbuf
				   [PX1 +
				    ((uint32) counterX - X1) * (PX2 -
								PX1) /
				    (X2 - X1)]);
		}
	}
	DESELECT(CS);
	DumpLocalBuf();
}

/* Open up the FAT filesystem and get ready to open a file */
void SetupFilesystem(struct partition_struct **partition,
		     struct fat_fs_struct **fs,
		     struct fat_dir_entry_struct *directory,
		     struct fat_dir_struct **dd)
{
	if (!sd_raw_init_status) {
		sd_raw_init();
		sd_raw_init_status = 1;
	}

	*partition = partition_open(sd_raw_read,
				    sd_raw_read_interval,
				    sd_raw_write,
				    sd_raw_write_interval, 0);
	if (!(*partition)) {
		/* If the partition did not open, assume the storage device
		 * is a "superfloppy", i.e. has no MBR.
		 */
		*partition = partition_open(sd_raw_read,
					    sd_raw_read_interval,
					    sd_raw_write,
					    sd_raw_write_interval, -1);
	}

	*fs = fat_open(*partition);

	fat_get_dir_entry_of_path(*fs, "/", directory);
	*dd = fat_open_dir(*fs, directory);
}

/* read image dimensions and fill in image struct */
void ReadImageHeader(struct fat_file_struct *fd, struct image *im)
{
	uint16 header[6];
	fat_read_file(fd, (uint8 *) header, 6);

	im->X = header[0];
	im->Y = header[1];
	im->encoding = header[2];
}

/* Get a row of unencoded pixels. Keep a local buffer to reduce file I/O overhead. */
void GetRow(struct fat_file_struct *fd, struct image *im, uint16 * rowbuf,
	    uint16 rowsize)
{
	int i;
	for (i = 0; i < rowsize; i++) {
		if (localbufindex == localbufsize) {
			localbufsize =
			    fat_read_file(fd, (uint8 *) localbuf,
					  LOCALBUF_MAX * 2) / 2;
			localbufindex = 0;
		}
		rowbuf[i] = localbuf[localbufindex++];
	}
}

/* dump the local buffer (in case not all the pixels are drawn, i.e. the picture
   runs off the edge of the screen) */
void DumpLocalBuf(void)
{
	localbufindex = localbufsize;
}

/* Get and decode a row of RLE-encoded pixels. */
void GetRowRLE(struct fat_file_struct *fd, struct image *im,
	       uint16 * rowbuf, uint16 rowsize)
{
	// TODO: make this work
	// TODO: use local buffer

	static uint16 nextcolor = 0, nextRL = 0;

	uint16 RLbuf[2] = { 0, 0 };

	while (nextRL && rowsize) {
		// use pixels left over from last time
		*(rowbuf++) = nextcolor;
		nextRL--;
		rowsize--;
	}

	while (rowsize) {
		// get the rest of the pixels for this row

		// read one color/runlength pair from the file and decode it into rowbuf.
		// TODO see if reading one RL/col pair at a time is causing unnecessary overhead
		fat_read_file(fd, (uint8 *) RLbuf, 4);
		while (RLbuf[0] && rowsize) {
			*(rowbuf++) = RLbuf[1];
			(*RLbuf)--;
			rowsize--;
		}
	}

	if (RLbuf[0]) {
		// there are more pixels in the most recent color/runlength pair; 
		// save them for next time
		nextRL = RLbuf[0];
		nextcolor = RLbuf[1];
	}
}

static inline uint16 min(uint16 A, uint16 B)
{
	return A > B ? B : A;
}
